#include <stdio.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>

#include <include/pf_net.h>
#include <include/pf_file.h>
#include <include/pf_internal.h>


/*********************************************************************************************
Function Name:  pf_get_socktype
Description  :  Get socket's type.
Inputs       :  int sockfd              : Socket fd.
Outputs      :  return value            : >=0 : Success. return socktype.
                                          -1  : Fail. see errno.
ErrorCodes   :  errno                   : sys call errno from setsockopt().
History      :
---------------------------------------------------------------------------------------------
Author                 Date                       Comments
lijz                   2011-6-24                  create
**********************************************************************************************/
static inline int pf_get_socktype(int sockfd);

/*********************************************************************************************
Function Name:  pf_fill_sockaddr
Description  :  Fill sockaddr data for special domain and port.
                Call getaddrinfo() convert IPv4/IPv6's domain and port to fill sockaddr data.
Inputs       :  pf_sockaddr_t* sockaddr      : sockaddr pointer to fill. Must be NOT NULL.
                const char* domain           : Host or IP numeric string. Must be NOT NULL.
                uint16_t port                : Port number.
                family                       : Protocal family. AF_INET or AF_INET6.
                socktype                     : Socket type. SOCK_STREAM or SOCK_DGRAM.
                int socktype                 : Output. socket's type.
                passive                      : passive flag.
                                               0 : sockaddr suiteble for connect(), sendto(), sendmsg().
                                               1 : sockaddr suitable for  bind()ing a sockefor listen()
Outputs      :  return value                 :  0 : Success.
                                               <0 : Fail. return -(errno).
                pf_sockaddr_t* sockaddr      : sockaddr data filled.
ErrorCodes   :  errno                        : sys call errno from getaddrinfo().
History      :
---------------------------------------------------------------------------------------------
Author                 Date                       Comments
lijz                   2011-6-24                  create
**********************************************************************************************/
static int pf_fill_sockaddr(pf_sockaddr_t* sockaddr, 
                            const char* domain, 
                            uint16_t port, 
                            int family, 
                            int socktype, 
                            int passive);

/*********************************************************************************************
Function Name:  pf_set_sockopt
Description  :  Sys call getsockopt(), enable or disable option on a socket.
Inputs       :  int sockfd                    : Socket fd.
                int level                     : level argument of getsockopt().
                int optname                   : optname argument of getsockopt().
                enum PF_OPT opt               : Option value. PF_ON or PF_OFF.
Outputs      :  return value                  : 0  : Success.
                                                -1 : Fail. Return -(errno).
ErrorCodes   :  errno                         : sys call errno from setsockopt().
History      :
---------------------------------------------------------------------------------------------
Author                 Date                       Comments
lijz                   2011-6-24                  create
**********************************************************************************************/
static inline int pf_set_sockopt(int sockfd, int level, int optname, enum PF_OPT opt);

/********************************************************************************************/

uint16_t pf_get_sockport(const pf_sockaddr_t* sockaddr)
{
    uint16_t ret = 0;

    assert(sockaddr !=NULL);

    switch(((struct sockaddr*)sockaddr)->sa_family)
    {
        case AF_INET:
            ret = htons(((struct sockaddr_in*)sockaddr)->sin_port);
            break;
        case AF_INET6:
            ret = htons(((struct sockaddr_in6*)sockaddr)->sin6_port);
            break;
        default:
            pf_set_errno(PF_INVALID);
            ret = 0;
    }

    return ret;
}

char* pf_get_sockaddr(const pf_sockaddr_t* sockaddr, char* str, int32_t str_len)
{
    assert(sockaddr != NULL);
    assert(str != NULL);

    return  (char*)inet_ntop(((struct sockaddr*)sockaddr)->sa_family, sockaddr, str, str_len);
}

int pf_listen(int sockfd, const char* domain, uint16_t port, pf_family_e family, uint16_t backlog)
{
    pf_sockaddr_t sockaddr;
    int socktype = -1;
    int ret      = 0;

    assert(domain != NULL);

    if((ret = socktype = pf_get_socktype(sockfd)) < 0)
    {
        return pf_errno();
    }

    if((ret = pf_fill_sockaddr(&sockaddr, domain, port, family, socktype, 1)) != 0)
    {
        return ret;
    }

    if(bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)
    {
        return pf_errno();
    }

    if(listen(sockfd, backlog) < 0)
    {
        return pf_errno();
    }

    return 0;
}

int pf_connect(int sockfd, const char* domain, uint16_t port, pf_family_e family, int32_t timeout)
{
    pf_sockaddr_t sockaddr;
    int socktype  = -1;
    int flags     = 0;
    int error     = -1;
    int ret       = 0;
    socklen_t len = 0;

    assert(domain != NULL);

    if((ret = socktype = pf_get_socktype(sockfd)) < 0)
    {
        return pf_errno();
    }

    flags = fcntl(sockfd, F_GETFL, 0);
    if(flags < 0)
    {
        return pf_errno();
    }
    if(!(flags & O_NONBLOCK) && fcntl(sockfd, F_SETFL, flags|O_NONBLOCK) != 0)
    {
        return pf_errno();
    }

    if((ret = pf_fill_sockaddr(&sockaddr, domain, port, family, socktype, 0)) != 0)
    {
        return ret;
    }

    do
    {
        ret = connect(sockfd, (const struct sockaddr *)&sockaddr, sizeof(sockaddr));
    }while(ret < 0 && (errno == EINTR));

    if(ret < 0)
    {
        switch(errno)
        {
            case EINPROGRESS:
                if (pf_wait_fd(sockfd, POLLOUT, timeout) != 0)
                {
                    error = 0;
                    len = sizeof(error);
                    if((getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == 0) && (error == 0))
                    {
                        ret = 0;
                    }
                    else
                    {
                        pf_set_errno(error != 0 ? error : errno);
                    }
                }
                else
                {
                    ret = PF_ETIMEDOUT;
                }
                break;
            default:
                return pf_errno();
                break;
        } /* switch(errno) */
    } /* if(ret < 0) */

    /* restore flags */
    if(!(flags & O_NONBLOCK) && fcntl(sockfd, F_SETFL, flags) != 0)
    {
        return pf_errno();
    }

    return ret;
}

int pf_set_keepalive(int sockfd, enum PF_OPT opt)
{
    return pf_set_sockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, opt);
}

int pf_set_oobinline(int sockfd, enum PF_OPT opt)
{
    return pf_set_sockopt(sockfd, SOL_SOCKET, SO_OOBINLINE, opt);
}

int pf_set_nodelay(int sockfd, enum PF_OPT opt)
{
    return pf_set_sockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, opt);
}

int pf_set_reuseaddr(int sockfd, enum PF_OPT opt)
{
    return pf_set_sockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, opt);
}

int pf_set_linger(int sockfd, struct linger* linger)
{
    if(setsockopt(sockfd, SOL_SOCKET, SO_LINGER, linger, sizeof(struct linger)) != 0)
    {
        return pf_errno();
    }

    return 0;
}

int pf_set_recvbuf(int sockfd, int32_t buf_size)
{
    if(setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &buf_size, sizeof(buf_size)) != 0)
    {
        return pf_errno();
    }

    return 0;
}

int pf_set_sendbuf(int sockfd, int32_t buf_size)
{

    if(setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &buf_size, sizeof(buf_size)) != 0)
    {
        return pf_errno();
    }

    return 0;
}

/********************************************************************************************/

static inline int pf_get_socktype(int sockfd)
{
    int socktype = 0;
    socklen_t len = sizeof(int);

    if(getsockopt(sockfd, SOL_SOCKET, SO_TYPE, &socktype, &len) != 0)
    {
        return -1;
    }

    return socktype;
}

static int pf_fill_sockaddr(pf_sockaddr_t* sockaddr, 
                            const char* domain, 
                            uint16_t port, 
                            int family, 
                            int socktype, 
                            int passive)
{
    struct addrinfo hints;
    struct addrinfo *ai   = NULL;
    struct addrinfo *next = NULL;
    char port_buff[16]    = {0};
    int error             = 0;
    int ret               = 0;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = family;
    hints.ai_socktype = socktype;
    if(passive)
    {
        hints.ai_flags = AI_PASSIVE;
    }

    snprintf(port_buff, sizeof(port_buff), "%"PRIu16, port);

    do
    {
        error = getaddrinfo(domain, port_buff, &hints, &ai);
    }while(error == EAI_AGAIN);

    if(error != 0)
    {
        switch(error)
        {
            case EAI_MEMORY:
                ret = PF_ENOMEM;
                break;
            case EAI_SYSTEM: /* already set errno */
                ret = pf_errno();
                break;
            case EAI_BADFLAGS:
            case EAI_FAIL:
            case EAI_FAMILY:
            case EAI_NONAME:
            case EAI_SERVICE:
            case EAI_SOCKTYPE:
            default:
                ret = PF_EINVAL;
                break;
        }/* switch(error) */

        return ret;
    }

    for(next = ai; next; next = next->ai_next)
    {
        if(next->ai_family==family && next->ai_socktype==socktype)
        {
            switch(family)
            {
                case AF_INET:
                    *((struct sockaddr_in*)sockaddr) = *((struct sockaddr_in*)(next->ai_addr));
                    break;
                case AF_INET6:
                    *((struct sockaddr_in6*)sockaddr) = *((struct sockaddr_in6*)(next->ai_addr));
                    break;
                default:  /* should not occur */
                    freeaddrinfo(ai);
                    return PF_EINVAL;
                    break;
            }

            freeaddrinfo(ai);
            return 0;
        }
    } /* for(next = ai; ...) */

    freeaddrinfo(ai);

    return PF_ENODATA;
}

static inline int pf_set_sockopt(int sockfd, int level, int optname, enum PF_OPT opt)
{
    int flags = 0;

    switch(opt)
    {
        case PF_ON:
            flags = 1;
            break;
        case PF_OFF:
            flags = 0;
            break;
        case PF_INVALID:
        default:
            return PF_EINVAL;
            break;

    }

    if(setsockopt(sockfd, level, optname, &flags, sizeof(flags)) != 0)
    {
        return pf_errno();
    }

    return 0;
}

